from lxml import etree
import logging
from utils import CommonUtils
from enum import Enum

logging.basicConfig(level=logging.INFO,  # 设置日志级别为DEBUG，即所有级别的日志消息都会被记录
                    format='%(asctime)s - %(levelname)s - %(message)s')  # 设置日志消息格式
# 语言关键词
LANGUAGE_KEYWORD = ['IELTS', 'TOEFL', 'CET', 'GMAT', 'GRE']
# 课程关键词
CourseKeywords = ["course", "courses", "Course", "Courses"]


# 定义xpath的枚举类
class HtmlXPath(Enum):
    PROGRAM_NAVIGATION_XPATH = "//table[contains(@class, 'table')]"
    PROGRAM_XPATH = "//*[@id='page-content']//article/*"
    DEGREE_XPATH = '//*[@id="block-w3css-subtheme-content"]//*[contains(concat(" ", normalize-space(@class), " "), ' \
                   '" view-content ")] '


# 定义专业中需要提取哪些类别的信息
class ProgramInfoCategory(Enum):
    program_description = "Program Description"
    minimum_admission_requirements = "Minimum Admission Requirements"
    program_requirements = "Program Requirements"
    program_length = "Program Length"
    time_limit = "Time Limit"


class HTMLTREE:
    def __init__(self, html_file_path, target_xpath):
        # html页面路径
        self.html_file_path = html_file_path
        # 目标xpath
        self.target_xpath = target_xpath
        with open(html_file_path, "rb") as f:
            html_content = f.read()
        # 解析html
        self.html_tree = etree.HTML(html_content)
        # 获取目标xpath的元素
        try:
            # 注意:
            self.root_content = self.html_tree.xpath(target_xpath.value)
        except etree.XPathEvalError as e:
            logging.error("XPath Error: %s" % e)
            self.root_content = []

    def parse_program_html(self):
        # 提取标题
        page_title_node_list = self.html_tree.xpath('//h1[contains(@class, "page-title")]')
        page_title_node = page_title_node_list[0]
        # 页面标题，即：专业名称
        page_title = CommonUtils.HtmlUtils.extract_text_from_tag_node(page_title_node)

        # 提取课程页面
        degree_url_ul_list = self.html_tree.xpath(
            "//*[contains(concat(' ', normalize-space(@class), ' '), ' list-unstyled ')]")
        degree_url_ul = degree_url_ul_list[0]
        a_tag = degree_url_ul.xpath("li[1]//a")[0]
        degree_url = a_tag.get("href")

        # 3. 解析html文件
        tree_structure = HTMLTREE.recursion_process(self.root_content, 2)

        # 4. 提取信息
        content_info_list = []
        language_info_list = []
        program_list = []

        # 过滤与初步提取数据
        for item in tree_structure:
            title = item.get("title")
            if title == "Quick Facts":
                program_list = item.get("children")
                break
            if title == "Program Overview":
                content_info_list = item.get("children")
            else:
                pass

        # 获取需要的专业
        required_program = self.get_required_program(program_list)
        """
        解析所需信息
        1. Program Description
        2. Minimum Admission Requirements
        3. Program Requirements
        4. Program Length
        5. Time Limit
        """
        # todo 可以将5个需要提取的内容定义为枚举类
        program_info_list_initial = required_program.get("children")
        program_info_list_structured = []

        for item_info in program_info_list_initial:
            item_info_title = item_info.get("title")
            item_info_list = self.process_program_every_info_structured(item_info)
            program_info_list_structured.append({
                "title": item_info_title,
                "content_list": item_info_list
            })

        program_infos = []
        for item_info in program_info_list_structured:
            title = item_info.get("title")
            contents = item_info.get("content_list")
            contents_unstructured = self.process_existed_program_info_unstructured(contents)
            program_infos.append({
                "title": title,
                "content": contents_unstructured
            })

        print(CommonUtils.FormatUtils.format_object_to_json(program_infos))

        # 3. 解析html文件
        return HTMLTREE.recursion_process(self.root_content, 2)

    def process_existed_program_info_unstructured(self, info_list):
        """
        将已存在的结构化信息转换为非结构化信息
        :param info_list:
        :return:
        """

        def flatten_ul_li_structure(ul_li_info_list):
            res_list = []
            for item in ul_li_info_list:
                if type(item) is str:
                    res_list.append(item)
                elif type(item) is list:
                    res_list.extend(flatten_ul_li_structure(item))
                else:
                    text = item.get("text")
                    res_list.append(text)
                    subitems = item.get("subitems")
                    if subitems:
                        subitems = flatten_ul_li_structure(subitems)
                        res_list.extend(subitems)
            return res_list

        res_list = []
        for item_info in info_list:
            if type(item_info) is str:
                res_list.append(item_info)
            else:
                res = flatten_ul_li_structure(item_info)
                res_list.extend(res)
        return res_list

    @staticmethod
    def process_program_every_info_structured(program_info):
        """
        解析指定专业信息，返回【结构化】的数据信息
        :param program_info:
        :return:
        """
        program_info_category_list = [item.value for item in ProgramInfoCategory]
        info_title = program_info.get("title")
        if info_title not in program_info_category_list:
            return
        info_list = program_info.get("children")
        info_list = CommonUtils.ListUtils.remove_invalid_nesting(info_list)
        res_list = []
        for item in info_list:
            if type(item) is str:
                res_list.append(item)
        return info_list

    @staticmethod
    def get_required_program(program_list):
        if len(program_list) <= 0:
            logging.error("获取需要的专业时, 专业列表为空")
            # todo 异常处理
        return program_list[0]

    # region 工具类
    @staticmethod
    def divide_node_list_by_tag(node_list, tag_name):
        """
        将节点列表按照[标签名称]进行分组
        :param node_list: 节点列表
        :param tag_name: 标签名称
        """
        groups = []
        # 初始化一个变量来跟踪[当前组]的开始位置
        start_index = 0
        # 遍历所有节点，找到tag_name标签的位置
        for i, node in enumerate(node_list):
            # 如果当前节点是tag_name
            if node.tag == tag_name:
                # 收集从上一个tag_name节点(即:位置为start_index的节点)之后的所有节点到当前节点（不包括当前节点）
                if i > start_index:
                    group = node_list[start_index:i]
                    groups.append(group)
                # 设置当前组的起始位置为当前节点的下一个位置
                start_index = i

        # 收集从最后一个tag_name到列表末尾的所有节点（如果有的话）
        if start_index < len(node_list):
            group = node_list[start_index:]
            groups.append(group)
            # 返回分组后的结果
        return groups

    @staticmethod
    # 递归
    def recursion_process(node_list, h_layer):
        """
        递归处理节点列表
        :param node_list: 节点列表
        :param h_layer: 当前节点所在的层级
        """
        divide_tag_name = "h{}".format(h_layer)
        groups = HTMLTREE.divide_node_list_by_tag(node_list, divide_tag_name)
        if len(groups) == 1:
            first_group = groups[0]
            if len(first_group) >= 1 and first_group[0].tag.startswith("h"):
                return [{
                    "title": CommonUtils.TextUtils.remove_consecutive_spaces(first_group[0].text),
                    "layer": h_layer,
                    "children": HTMLTREE.classify_process_node(first_group[1:len(first_group)])
                }]
        if len(groups) <= 1 and groups is not None:
            return groups
        else:
            res = []
            for group in groups:
                # 递归处理下一层
                tag_value = group[0]
                if type(tag_value) is etree._Element and tag_value.tag.startswith("h"):
                    pass
                else:
                    logging.info("特殊节点{}".format(tag_value))
                    continue

                value = HTMLTREE.recursion_process(group[1:len(group)], h_layer + 1)
                if value is not None and len(value) >= 1:
                    title = CommonUtils.TextUtils.remove_consecutive_spaces(group[0].text)
                    if title is not None and len(title) > 0 and title is not ' ':
                        res.append({
                            "title": title,
                            "layer": h_layer,
                            "children": HTMLTREE.classify_process_node(value)
                        })
            return res

    @staticmethod
    def classify_process_node(node_list):
        """
        根据节点类型进行分类处理
        :param node_list: 节点列表
        """
        if type(node_list) is list:
            item_list = []
            for item_node in node_list:
                value = HTMLTREE.classify_process_node(item_node)
                item_list.append(value)
            return item_list
        elif type(node_list) is etree._Element:
            if node_list.tag == "ul":
                return CommonUtils.HtmlUtils.process_ul(node_list)
            else:
                return CommonUtils.HtmlUtils.extract_text_from_tag_node(node_list)
        else:
            return node_list
        # endregion)
        return program_list[0]
    # endregion


# file_path = r"G:\ututes\Code\tools\ututes_html_process\resources\programs\Applied Computing.html"
file_path = r"G:\ututes\Code\tools\ututes_html_process\resources\programs\Adult Education and Community Development.html"

html_tree = HTMLTREE(file_path, HtmlXPath.PROGRAM_XPATH)
data = html_tree.parse_program_html()
